package depgraph

import (
    "log"
)

type Dependency interface {
    Build() bool
    ModTime() uint64
    Label() string
    Output() string
    Dependencies() []string
    String() string
}

type DependencyGraph map[string] *dependencyGraphElement
type dependencyGraphElement struct {
    obj Dependency
    deps []*dependencyGraphElement
    success chan bool
}

func New() *DependencyGraph {
    graphObj := make(DependencyGraph)
    depGraph := &graphObj
    return depGraph
}

func (depGraph *DependencyGraph) addDependencyDummy(depName string) *dependencyGraphElement {
    id := depName
    
    // return a valid pointer even if we have no content in that graph element yet
    if dep, contained := (*depGraph)[id]; contained {
        return dep
    }
    
    dep := new(dependencyGraphElement)
    dep.success = make(chan bool)
    (*depGraph)[id] = dep
    return dep
}

func (depGraph *DependencyGraph) AddDependency(depObj Dependency) *dependencyGraphElement {
    id := depObj.Label()
    dep, contained := (*depGraph)[id]
    
    // here we actually fill content, no matter if the graph element was
    // inserted yet or not
    if !contained {
        dep = new(dependencyGraphElement)
        dep.success = make(chan bool)
        (*depGraph)[id] = dep
    }
    
    dep.obj = depObj
    depObjDeps := depObj.Dependencies()
    dep.deps = make([]*dependencyGraphElement, len(depObjDeps))
    // hook up all dependencies recursively
    for i, depName := range depObjDeps {
        dep.deps[i] = depGraph.addDependencyDummy(depName)
    }
    
    return dep
}

func (depGraph *DependencyGraph) Build(targets []string) {
    if len(targets) == 0 {
        // if no targets are specified, make all
        // all excludes "clean" and "nuke"
        for _, el := range (*depGraph) {
            go el.build()
        }
    } else {
        for _, trg := range targets {
            dep, found := (*depGraph)[trg]
            if found {
                go dep.build()
            } else {
                log.Stderrf("could not find target \"%s\"", trg)
            }
        }
    }
}

func (self *dependencyGraphElement) build() {
    // wait for each dependency
    for _, dep := range self.deps {
        success := <-dep.success
        dep.success <-success
        if !success {
            // dependecy build failed, so we cannot hope
            // to succeed
            self.success <- false
            return
        }
    }
    if self.needsUpdating() {
        self.success <- self.obj.Build()
    } else {
        self.success <- true
    }
}

func (self *dependencyGraphElement) needsUpdating() bool {
    depMod := uint64(0)
    for _, dep := range self.deps {
        depMod = max(depMod, dep.obj.ModTime())
    }
    return depMod > self.obj.ModTime()
}

func max(a, b uint64) uint64 {
    if a > b {
        return a
    }
    return b
}

